from multiprocessing import Process
import time
import requests
from libs.conn import *
from datetime import datetime, timedelta
from libs.utils import *
from libs.render_template import render_template
# from send_sms_tencent import send_sms_tencent
from libs.send_email import send_email

# 告警触发逻辑
# 当发现有设备断网超过设定时间，或者断网次数超过设定次数，则触发告警
# 告警通知逻辑
# 短信、邮箱、钉钉、微信，需要选择一种或多种发送。或者选择中继服务器，将会先发送给中继服务器，然后由中继服务器发送。

def handleTemplateType(template, template_type = 'common'):
    types = ['maxtime', 'maxcount', 'common', 'test']
    templates = []
    lines = template.split('\n')
    t = 'common'
    temp_template = ''
    for line in lines:
        # if line.strip() == '': # 空行跳过
        #     continue
        for key in types: # 检测是否需要匹配新的模板类型
            if line.startswith('[' + key + ']'):
                templates.append((t, temp_template.rstrip()))
                line = line[len('[' + key + ']'):]
                t = key
                temp_template = ''
                break
        
        temp_template += line.strip() + '\n'
    
    templates.append((t, temp_template.rstrip()))
    result = ''
    for item in templates:
        if item[0] == template_type or item[0] == 'common':
            result += item[1]
            
    return result.strip()


def render_alarm_template(plan, default_template = ''):
    if is_string_valid(plan['plan_template']):
        default_template = plan['plan_template']
    if not is_string_valid(default_template):
        if plan['alarm_type'] == 'maxtime':
            default_template = r'设备 {{ device_name }} 断网超过 {{ format_seconds(maxtime) }}'
        elif plan['alarm_type'] == 'maxcount':
            default_template = r'设备 {{ device_name }} 在 {{ format_seconds(rangetime) }} 内断网超过 {{ maxcount }} 次'
        elif plan['alarm_type'] == 'test':
            default_template = r'设备 {{ device_name }} 报警测试'
    
    default_template = handleTemplateType(default_template, plan['alarm_type'])

    client_name = plan['client_name']
    if not is_string_valid(client_name):
        client_name = plan['client_id']

    offlinetime_list = []
    if is_string_valid(plan['plan_count']):
        offlinetime_list = plan['plan_count'].split(',')

    print(default_template)
    return render_template(default_template, {
        "pow": pow,
        "max": max,
        "min": min,
        'datetime': datetime,
        'format_seconds': format_seconds,
        'alarm_type': plan['alarm_type'],
        'maxcount': plan['plan_maxcount'],
        'maxtime': plan['plan_maxtime'],
        'rangetime': plan['plan_rangetime'],
        'begintime': plan['plan_begintime'],
        'endtime': plan['plan_endtime'],
        'plan_name': plan['plan_name'],
        'plan_id': plan['plan_id'],
        'device_id': plan['client_id'],
        'device_name': client_name,
        'device_ip': plan['client_ip'],
        'offlinetime': plan['offlinetime'],
        'offlinetime_list': offlinetime_list,
        'right_brace': '}',
        'left_brace': '{'
    })

def check_alarm():
    while True:
        try:
            print("开始检查告警")
            # 取出来所有的告警计划，要求为启用状态，并且最大时间或者最大次数大于0，且客户端的主动检查时间或者主动在线时间不为空，且客户端的密钥不为空或者有启用的任务
            # 因为如果还没开始检测，或者测试任务都处于禁用状态，那么就没有必要进行告警
            alarm_plans = fetchall('''SELECT a.*, b.client_name, b.client_name, b.client_ip, b.client_check_time, b.client_alive_time 
                                   FROM alarm_plans a 
                                   INNER JOIN clients b 
                                   ON a.client_id = b.client_id 
                                   WHERE a.plan_enable = 1 
                                   and (a.plan_maxtime > 0 or a.plan_maxcount > 0) 
                                   and (b.client_check_time is not null or b.client_alive_time is not null) 
                                   and (b.client_secret is not null or (SELECT COUNT(*) FROM tasks WHERE tasks.client_id = b.client_id and tasks.task_enable = 1) > 0)''')
            # print(alarm_plans)
            for plan in alarm_plans:
                client_id = plan['client_id']
                plan_maxtime = plan['plan_maxtime']
                plan_maxcount = plan['plan_maxcount']
                plan_rangetime = plan['plan_rangetime']
                plan_begintime = plan['plan_begintime']
                plan_endtime = plan['plan_endtime']

                if is_string_valid(plan_begintime) and is_string_valid(plan_endtime):
                    current_time = datetime.now().strftime('%H:%M')
                    # print("当前时间：", current_time)
                    # print("计划开始时间：", plan_begintime)
                    # print("计划结束时间：", plan_endtime)
                    if current_time < plan_begintime or current_time > plan_endtime: # 如果当前时间不在计划的时间范围内，则跳过
                        continue

                if plan_maxtime > 0 and plan['plan_active'] != 1: # 如果计划最大时间大于0，则表示需要判断断网时间；如果计划已经告警了，则无需重复告警
                    # 获取客户端的最后在线时间
                    offlinetime_str = ''
                    client_check_time = plan['client_check_time']
                    client_alive_time = plan['client_alive_time']
                    if client_check_time is not None:
                        offlinetime_str = client_check_time
                    if client_alive_time is not None and client_alive_time > offlinetime_str:
                        offlinetime_str = client_alive_time
                    plan['offlinetime'] = offlinetime_str
                    offlinetime_split = offlinetime_str.split(' ')
                    if is_string_valid(plan_begintime) and offlinetime_split[1] < plan_begintime: # 如果客户端的最后在线时间早于计划开始时间，则从计划开始时间开始计算
                        offlinetime_str = offlinetime_split[0] + ' ' + plan_begintime + ':00'
                        print(plan_begintime, offlinetime_split[0], offlinetime_str)
                    # 获取当前时间
                    current_time = datetime.now()
                    # 计算时间差
                    time_difference = current_time - parse_time(offlinetime_str)
                    # 如果时间差大于最大允许时间，则记录告警信息
                    if time_difference.total_seconds() > plan_maxtime:
                        plan['alarm_type'] = 'maxtime'
                        message = render_alarm_template(plan)
                        print(message)
                        execute("INSERT INTO alarm_records (client_id, plan_id, alarm_time, alarm_content, alarm_noticetype, alarm_noticed) VALUES (?, ?, ?, ?, ?, ?)", (client_id, plan['plan_id'], get_time(), message, plan['plan_noticetype'], 0))
                        # 更新告警计划的状态为正在告警
                        execute("UPDATE alarm_plans SET plan_active = 1 WHERE plan_id = ?", (plan['plan_id'],))

                if plan_maxcount > 0: # 如果计划最大次数大于0，则表示需要判断断网次数
                    offlinetime_list = []
                    if is_string_valid(plan['plan_count']):
                        offlinetime_list = plan['plan_count'].split(',')
                    # 获取当前时间
                    current_time = datetime.now()
                    new_offlines = []
                    for client_offline in offlinetime_list:
                        if client_offline == '':
                            continue
                        offline_time = parse_time(client_offline)
                        # 计算时间差
                        time_difference = current_time - offline_time
                        # 如果时间差大于时间范围，则忽略
                        if time_difference.total_seconds() > plan_rangetime:
                            continue
                        new_offlines.append(client_offline)
                    # 如果断网次数大于最大允许次数，则记录告警信息
                    if len(new_offlines) >= plan_maxcount:
                        plan['alarm_type'] = 'maxcount'
                        message = render_alarm_template(plan)
                        print(message)
                        execute("INSERT INTO alarm_records (client_id, plan_id, alarm_time, alarm_content, alarm_noticetype, alarm_noticed) VALUES (?, ?, ?, ?, ?, ?)", (client_id, plan['plan_id'], get_time(), message, plan['plan_noticetype'], 0))
                        # 更新断网次数
                        # new_offlines = new_offlines[plan_maxcount:] # 只保留最新的断网次数
                        execute("UPDATE alarm_plans SET plan_count = ? WHERE plan_id = ?", ('', plan['plan_id']))
            
        except Exception as e:
            print(f"Error: {e}")
        
        time.sleep(10)


def send_aliyun_sms(notice_target, notice_content):
    # 发送阿里云短信通知
    print('暂未实现阿里云短信接口')
    return False, ''

def send_tencent_sms(notice_target, notice_content):
    # 发送腾讯云短信通知
    print('暂未实现腾讯云短信接口')
    return False, ''

def send_sms(notice_target, notice_content):
    # 发送短信通知
    sms_type = get_config('sms_type')
    if sms_type == 'aliyun':
        return send_aliyun_sms(notice_target, notice_content)
    elif sms_type == 'tencent':
        return send_tencent_sms(notice_target, notice_content)
    else:
        print('暂未实现短信接口')
        return False, ''

def send_wechat(notice_target, notice_content):
    # 发送微信通知
    print('暂未实现微信接口')
    return False, ''

def send_dingtalk(notice_target, notice_content):
    # 发送钉钉通知
    print('暂未实现钉钉接口')
    return False, ''

def send_relay(notice_type, notice_target, notice_title, notice_content, relay_ttl = 1):
    if relay_ttl == 0:
        return False, '已达到最大中继次数'
    try:
        # 发送中继通知
        relay_url = get_config('relay_url')
        app_id = get_config('app_id')
        app_secret = get_config('app_secret')
        check_code = hash_message(notice_content, app_secret) # 仅用于防止伪造消息，但不保证消息重放
        res = requests.post(f"{relay_url}/alarm_notice", json={
            'app_id': app_id,
            'notice_type': notice_type, 
            'notice_target': notice_target, 
            'notice_title': notice_title,
            'notice_content': notice_content, 
            'check_code': check_code,
            'relay_ttl': relay_ttl - 1
        }, timeout=30)
        if res.status_code == 200:
            result = res.json()
            if result['status'] == 'ok':
                return True, result['data']
            else:
                print(f"Error: {result['data']}")
                return False, result['data']
        else:
            print(f"Error: {res.text}")
            return False, result['data']
    except Exception as e:
        print(f"Error: {e}")
        return False, str(e)

def send_notice(notice_type, notice_target, notice_title, notice_content, notice_force_relay = False, relay_ttl = 1):
    if notice_force_relay:
        return send_relay(notice_type, notice_target, notice_title, notice_content, relay_ttl)
    if notice_type == 'email':
        return send_email(notice_target, notice_title, notice_content)
    elif notice_type == 'sms':
        return send_sms(notice_target, notice_content)
    elif notice_type == 'wechat':
        return send_wechat(notice_target, notice_content)
    elif notice_type == 'dingtalk':
        return send_dingtalk(notice_target, notice_content)


last_notice_time = datetime.now() - timedelta(hours=24)
def check_notice():
    global last_notice_time
    while True:
        print("开始处理告警通知")
        try:
            alarm_notice_interval = int(get_config('alarm_notice_interval'))
            app_id= get_config('app_id')
            app_name = get_config('app_name')
            if datetime.now() - last_notice_time < timedelta(seconds=alarm_notice_interval):
                time.sleep(5) # 如果距离上次通知时间不足间隔时间，则等待
                continue
            # 如果距离上次通知时间超过间隔时间，则生成通知
            templates = {
                'email': '',
                'sms': '',
                'wechat': '',
                'dingtalk': ''
            }
            alarm_records = fetchall('SELECT * FROM alarm_records WHERE alarm_time > ? and alarm_noticed = 0 and LENGTH(alarm_noticetype) > 1', (last_notice_time.strftime('%Y-%m-%d %H:%M:%S'),))
            for alarm_record in alarm_records:
                notice_content = f"{alarm_record['alarm_time']} - {alarm_record['alarm_content']}\n"
                for key in templates.keys():
                    if key in alarm_record['alarm_noticetype']:
                        templates[key] += notice_content
            current_time = get_time()
            for key, value in templates.items():
                if value != '':
                    app_name = get_config('app_name')
                    if not is_string_valid(app_name):
                        app_name = get_config('app_id')
                    value = f"从 {app_name} 发出的告警信息如下，请及时处理！\n" + value
                    notice_target = get_config(f'{key}_common_target')
                    execute('INSERT INTO alarm_notices (notice_source, app_id, app_name, notice_time, notice_content, notice_type, notice_target, notice_sent) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', ('local', app_id, app_name, current_time, value, key, notice_target, 0))
                    conn, cursor = create_conn()
                    for alarm_record in alarm_records:
                        execute('UPDATE alarm_records SET alarm_noticed = 1 WHERE alarm_id = ?', (alarm_record['alarm_id'],))
                    conn.commit()
                    cursor.close()
                    conn.close()
                    print('生成通知成功')
                    
            # 取出所有要通知的记录，逐个进行通知(取通知的原则是取就近的一天内没发送的，并且一次只取10个，避免一次性通知太多导致接口被查封。不会重复发送失败的，避免反复失败)
            alarm_notices = fetchall('SELECT * FROM alarm_notices WHERE notice_time > ? and notice_sent = 0 limit 10', (last_notice_time.strftime('%Y-%m-%d %H:%M:%S'),))
            notice_force_relay = get_config('notice_force_relay') == '1'
            for alarm_notice in alarm_notices:
                notice_type = alarm_notice['notice_type']
                notice_target = alarm_notice['notice_target']
                notice_content = alarm_notice['notice_content']
                if get_config(f'{notice_type}_enable') != '1':
                    print(f"{notice_type}通知未启用，跳过")
                    continue
                
                notice_title = get_config(f"{notice_type}_subject")
                notice_ok, message = send_notice(notice_type, notice_target, notice_title, notice_content, notice_force_relay, relay_ttl=3) # 如果有中继，最多中继三次
                notice_id = alarm_notice["notice_id"]
                if notice_ok:
                    execute('UPDATE alarm_notices SET notice_sent = 1 WHERE notice_id = ?', (notice_id,))
                    execute("INSERT INTO system_logs (log_content, log_time) VALUES (?, ?)", (f"发送{notice_type}通知【{notice_title}】成功，notice_id: {notice_id}", get_time()))
                else:
                    execute('UPDATE alarm_notices SET notice_sent = 2 WHERE notice_id = ?', (notice_id,))
                    execute("INSERT INTO system_logs (log_content, log_time) VALUES (?, ?)", (f"发送{notice_type}通知【{notice_title}】失败，notice_id: {notice_id}，失败原因: {message}", get_time()))

            last_notice_time = datetime.now() - timedelta(hours=24)

        except Exception as e:
            print(f"Error: {e}")

        time.sleep(10)



def check_alarm_process():
    p = Process(target=check_alarm)
    #p.daemon = True # 设置为守护进程，如果主进程退出，子进程也会退出
    p.start()
    return p.pid

def check_notcie_process():
    p = Process(target=check_notice)
    #p.daemon = True # 设置为守护进程，如果主进程退出，子进程也会退出
    p.start()
    return p.pid
      